#!/usr/bin/env python
# -*- coding: UTF-8 -*-
'''
@Project ：MicInter_IPCamera 
@File    ：迈克尔逊干涉.py
@IDE     ：PyCharm 
@Author  ：董文捷
@Date    ：2025/11/4 22:58 
'''
import cv2
import numpy as np
import os
import time
import threading
import queue
import json
import random
from paho.mqtt import client as mqtt_client
from ultralytics import YOLO
from tqdm import tqdm
from scipy.signal import find_peaks
import matplotlib

matplotlib.use('Agg')  # 使用非交互式后端，避免子线程GUI警告
import matplotlib.pyplot as plt

try:
    from PIL import Image, ImageTk
    from PIL import ImageDraw, ImageFont

    HAS_PIL = True
except ImportError:
    HAS_PIL = False
    print("警告: PIL/Pillow未安装，GUI功能可能受限。请运行: pip install Pillow")
import tkinter as tk
from tkinter import ttk, messagebox, filedialog


# --- Text rendering helpers for Chinese support ---
def _find_cn_font_path():
    """Best-effort detection of a Chinese-capable TTF/TTC on Windows."""
    candidate_paths = [
        r"C:\\Windows\\Fonts\\msyh.ttc",  # Microsoft YaHei
        r"C:\\Windows\\Fonts\\msyhl.ttc",  # Microsoft YaHei Light
        r"C:\\Windows\\Fonts\\simhei.ttf",  # SimHei
        r"C:\\Windows\\Fonts\\simsun.ttc",  # SimSun
        r"C:\\Windows\\Fonts\\Deng.ttf",  # DengXian
    ]
    for p in candidate_paths:
        try:
            if os.path.exists(p):
                return p
        except Exception:
            pass
    return None


def put_text_multilang_bgr(img_bgr, text, xy, font_scale=1.0, color=(255, 255, 255), thickness=2):
    """
    Draw text on a BGR numpy image. Uses PIL+TrueType when available to support Chinese.
    Falls back to cv2.putText otherwise.
    xy: (x, y) top-left in pixels
    """
    if HAS_PIL:
        try:
            font_path = _find_cn_font_path()
            # Map scale to roughly point size
            font_size = max(12, int(22 * float(font_scale)))
            if font_path:
                font = ImageFont.truetype(font_path, font_size)
            else:
                font = ImageFont.load_default()

            # Convert to RGB PIL Image
            img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
            pil_img = Image.fromarray(img_rgb)
            draw = ImageDraw.Draw(pil_img)
            # Stroke to emulate thickness
            stroke_w = max(1, int(thickness))
            draw.text(xy, str(text), fill=(int(color[2]), int(color[1]), int(color[0])),
                      font=font, stroke_width=stroke_w, stroke_fill=(0, 0, 0))
            # Convert back to BGR
            result = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
            img_bgr[:, :, :] = result
            return img_bgr
        except Exception:
            pass

    # Fallback to OpenCV (no Chinese support)
    cv2.putText(img_bgr, str(text), xy, cv2.FONT_HERSHEY_SIMPLEX, float(font_scale), color, int(thickness))
    return img_bgr


class MqttController:
    def __init__(self,
                 broker='b00001b0.ala.cn-hangzhou.emqxsl.cn',
                 port=8883,
                 publish_topic='Android_Get',
                 subscribe_topic='Android_Set',
                 username='username',
                 password='password'):
        self.broker = broker
        self.port = port
        self.publish_topic = publish_topic
        self.subscribe_topic = subscribe_topic
        self.username = username
        self.password = password
        self.client = None
        self.connected = False
        self.stopped = False

    def connect(self):
        client_id = f'python-mqtt-micinter-{random.randint(0, 1000)}'

        def on_connect(client, userdata, flags, rc, properties=None):
            if rc == 0:
                self.connected = True
                self.stopped = False
                print(f"✅ 已成功连接到 MQTT 服务器：{self.broker}:{self.port}")
                try:
                    client.subscribe(self.subscribe_topic)
                    print(f"📥 已订阅接收主题：{self.subscribe_topic}")
                except Exception as e:
                    print(f"❌ 订阅失败：{e}")
            else:
                print(f"❌ 连接 MQTT 服务器失败，错误码：{rc}")

        def on_message(client, userdata, msg):
            try:
                payload = msg.payload.decode('utf-8')
            except Exception:
                payload = str(msg.payload)
            print(f"📩 收到消息 | 主题：{msg.topic} | 内容：{payload}")

        self.client = mqtt_client.Client(
            client_id=client_id,
            callback_api_version=mqtt_client.CallbackAPIVersion.VERSION2
        )
        self.client.on_connect = on_connect
        self.client.on_message = on_message

        try:
            self.client.tls_set()
            self.client.tls_insecure_set(True)
        except Exception as e:
            print(f"❌ 配置 TLS/SSL 时发生异常：{e}")

        if self.username:
            self.client.username_pw_set(self.username, self.password)

        try:
            self.client.connect(self.broker, self.port)
            self.client.loop_start()
        except Exception as e:
            print(f"❌ 连接 MQTT 服务器时发生异常：{e}")

    def send_command(self, command):
        if self.stopped or (not self.client) or (not self.connected):
            return
        try:
            result = self.client.publish(self.publish_topic, command)
            if result[0] == 0:
                print(f"✅ 已发送电机命令 | 主题：{self.publish_topic} | 内容：{command}")
            else:
                print(f"❌ 电机命令发送失败 | 主题：{self.publish_topic} | 内容：{command}")
        except Exception as e:
            print(f"❌ 发布命令异常：{e}")

    def stop(self):
        try:
            if self.client:
                self.client.loop_stop()
                self.client.disconnect()
                print("✅ MQTT 已断开")
                self.client = None
            self.connected = False
            self.stopped = True
        except Exception as e:
            print(f"❌ 断开 MQTT 时异常：{e}")


class ThroughputDetector:
    """吞吐检测器 - 基于中线交点法"""

    def __init__(self, model_path='runs/detect/train3/weights/best.pt'):
        """
        初始化吞吐检测器（中线交点法）
        """
        print(f"🔍 加载训练好的模型: {model_path}")

        if not os.path.exists(model_path):
            raise FileNotFoundError(f"❌ 找不到模型文件: {model_path}")

        try:
            self.model = YOLO(model_path)
            print("✅ 模型加载成功")
        except Exception as e:
            raise RuntimeError(f"❌ 模型加载失败: {str(e)}")

        # 配置参数
        self.confidence_threshold = 0.1

        # 吞吐检测状态
        self.brightness_values = []  # 存储所有亮度值
        self.frame_numbers = []  # 存储对应的帧号
        self.crossing_points = []  # 存储中线交点位置
        self.current_throughput = 0  # 当前吞吐数量
        self.crossing_events = []  # 存储交点事件
        self.midline = 0.5  # 初始中线值

        print("✅ 中线交点法吞吐器初始化完成")

    def get_center_region_brightness(self, frame, center_point, radius=20):
        """
        获取中心区域的亮度值
        """
        x, y = int(center_point[0]), int(center_point[1])
        h, w = frame.shape[:2]

        # 确保区域在图像范围内
        x1 = max(0, x - radius)
        y1 = max(0, y - radius)
        x2 = min(w, x + radius)
        y2 = min(h, y + radius)

        if x1 >= x2 or y1 >= y2:
            return 0

        # 提取区域并计算亮度
        region = frame[y1:y2, x1:x2]
        if len(region) == 0:
            return 0

        if len(region.shape) == 3:
            region = cv2.cvtColor(region, cv2.COLOR_BGR2GRAY)

        brightness = np.mean(region) / 255.0
        return brightness

    def detect_center_point(self, frame):
        """
        检测帧中的中心点
        """
        try:
            results = self.model(frame, imgsz=640, conf=self.confidence_threshold, verbose=False)

            for result in results:
                if hasattr(result, 'boxes') and result.boxes is not None and len(result.boxes) > 0:
                    boxes = result.boxes.xyxy.cpu().numpy()
                    confidences = result.boxes.conf.cpu().numpy()

                    # 取置信度最高的检测框
                    if len(confidences) > 0:
                        max_conf_idx = np.argmax(confidences)
                        box = boxes[max_conf_idx]
                        conf = confidences[max_conf_idx]

                        # 计算中心点
                        x_center = (box[0] + box[2]) / 2
                        y_center = (box[1] + box[3]) / 2

                        return (x_center, y_center), conf

            return None, 0

        except Exception as e:
            print(f"⚠️ 中心点检测错误: {str(e)}")
            return None, 0

    def detect_midline_crossings(self):
        """
        检测亮度曲线与中线的交点
        返回：交点数量（吞吐次数 = 有效交点对数）
        过滤单个交点：只计算成对的交点（一个上升沿和一个下降沿）
        """
        if len(self.brightness_values) < 2:
            return 0, []

        # 计算中线（亮度平均值）
        self.midline = np.mean(self.brightness_values)

        # 检测所有可能的交点
        all_crossings = []
        for i in range(1, len(self.brightness_values)):
            prev_val = self.brightness_values[i - 1]
            curr_val = self.brightness_values[i]

            # 检查是否穿过中线
            if (prev_val < self.midline and curr_val >= self.midline) or \
                    (prev_val > self.midline and curr_val <= self.midline):
                all_crossings.append(i)

        # 过滤单个交点 - 只保留成对的交点
        valid_crossings = []
        i = 0
        while i < len(all_crossings) - 1:
            # 检查当前交点和下一个交点是否构成一对（一个上升一个下降）
            current_idx = all_crossings[i]
            next_idx = all_crossings[i + 1]

            # 获取交点的亮度值
            current_val = self.brightness_values[current_idx]
            next_val = self.brightness_values[next_idx]

            # 检查是否构成一对（一个高于中线，一个低于中线）
            if (current_val >= self.midline and next_val <= self.midline) or \
                    (current_val <= self.midline and next_val >= self.midline):
                valid_crossings.append(current_idx)
                valid_crossings.append(next_idx)
                i += 2  # 跳过下一对
            else:
                i += 1  # 移动到下一个点

        # 吞吐次数 = 有效交点对数
        throughput_count = len(valid_crossings) // 2

        return throughput_count, valid_crossings

    def update_throughput_count(self, frame_idx, brightness):
        """
        实时更新吞吐数量
        """
        if len(self.brightness_values) < 2:
            return self.current_throughput

        # 更新中线值
        if len(self.brightness_values) > 10:  # 至少有10个样本后再计算中线
            self.midline = np.mean(self.brightness_values)

        # 检查是否穿过中线
        prev_val = self.brightness_values[-2] if len(self.brightness_values) >= 2 else brightness
        curr_val = brightness

        # 检查是否穿过中线
        crossed = False
        direction = None

        if prev_val < self.midline and curr_val >= self.midline:
            crossed = True
            direction = "up"
        elif prev_val > self.midline and curr_val <= self.midline:
            crossed = True
            direction = "down"

        if crossed:
            # 记录交点事件
            self.crossing_events.append({
                'frame': frame_idx,
                'brightness': curr_val,
                'direction': direction,
                'time': time.time()
            })

            # 检查是否形成有效对
            if len(self.crossing_events) >= 2:
                last_event = self.crossing_events[-1]
                prev_event = self.crossing_events[-2]

                # 如果方向相反且时间间隔合理（避免误检）
                if (last_event['direction'] != prev_event['direction'] and
                        last_event['frame'] - prev_event['frame'] < 100):  # 最大100帧间隔
                    self.current_throughput += 1
                    # 移除已配对的事件
                    self.crossing_events = self.crossing_events[:-2]

        return self.current_throughput

    def draw_detection_info(self, frame, center_point, brightness, throughput_count):
        """
        在帧上绘制检测信息和吞吐数量
        """
        display_frame = frame.copy()

        # 获取帧的尺寸，用于自适应字体大小
        frame_height, frame_width = display_frame.shape[:2]
        base_font_scale = frame_width / 1280.0  # 基于1280宽度进行缩放
        base_font_scale = max(0.5, min(2.0, base_font_scale))  # 限制字体大小范围
        font_thickness = max(1, int(2 * base_font_scale))

        # 绘制中心点
        if center_point:
            x, y = int(center_point[0]), int(center_point[1])
            radius1 = max(5, int(10 * base_font_scale))
            radius2 = max(10, int(20 * base_font_scale))
            cv2.circle(display_frame, (x, y), radius1, (0, 255, 0), max(1, int(2 * base_font_scale)))
            cv2.circle(display_frame, (x, y), radius2, (0, 255, 0), max(1, int(1 * base_font_scale)))

        # 绘制吞吐数量（中文）
        text_y = max(30, int(50 * base_font_scale))
        put_text_multilang_bgr(
            display_frame,
            f"吞吐量: {throughput_count}",
            (max(10, int(20 * base_font_scale)), text_y),
            base_font_scale,
            (0, 255, 0),
            font_thickness
        )

        # 绘制亮度值（中文）
        text_y = max(60, int(90 * base_font_scale))
        put_text_multilang_bgr(
            display_frame,
            f"亮度: {brightness:.3f}",
            (max(10, int(20 * base_font_scale)), text_y),
            base_font_scale * 0.9,
            (0, 255, 255),
            font_thickness
        )

        # 绘制中线值（中文）
        text_y = max(90, int(130 * base_font_scale))
        put_text_multilang_bgr(
            display_frame,
            f"中线: {self.midline:.3f}",
            (max(10, int(20 * base_font_scale)), text_y),
            base_font_scale * 0.9,
            (255, 255, 0),
            font_thickness
        )

        # 绘制帧信息（中文）
        text_y = max(120, int(170 * base_font_scale))
        put_text_multilang_bgr(
            display_frame,
            f"帧数: {len(self.brightness_values)}",
            (max(10, int(20 * base_font_scale)), text_y),
            base_font_scale * 0.9,
            (255, 0, 255),
            font_thickness
        )

        return display_frame

    def plot_analysis(self, title_name):
        """
        绘制中线交点分析图表，保存为PNG并返回路径
        """
        if len(self.brightness_values) < 2:
            return None, 0, getattr(self, 'midline', 0.5)

        throughput_count, crossings = self.detect_midline_crossings()

        plt.figure(figsize=(12, 8))
        frames = range(len(self.brightness_values))
        plt.plot(frames, self.brightness_values, 'b-', label='Brightness', alpha=0.8, linewidth=2)

        midline_val = getattr(self, 'midline', np.mean(self.brightness_values))
        plt.axhline(y=midline_val, color='r', linestyle='-', alpha=0.7, linewidth=2,
                    label=f'Midline: {midline_val:.3f}')

        if crossings:
            crossing_values = [self.brightness_values[i] for i in crossings]
            plt.plot(crossings, crossing_values, 'ro', markersize=6,
                     label=f'Valid Crossings ({len(crossings)})')

        plt.xlabel('Frame Number')
        plt.ylabel('Brightness (0-1)')
        plt.title(f'Midline Crossing Analysis - {title_name}\nThroughput Count: {throughput_count}')
        plt.legend()
        plt.grid(True, alpha=0.3)
        plt.tight_layout()

        os.makedirs('throughput_analysis', exist_ok=True)
        safe_name = ''.join(c if c.isalnum() or c in ('-', '_') else '_' for c in title_name)
        plot_path = f'throughput_analysis/{safe_name}_midline_analysis.png'
        plt.savefig(plot_path, dpi=300, bbox_inches='tight')
        plt.close()

        return plot_path, throughput_count, midline_val

    def reset(self):
        """重置检测器状态"""
        self.brightness_values = []
        self.frame_numbers = []
        self.crossing_points = []
        self.current_throughput = 0
        self.crossing_events = []
        self.midline = 0.5


class ConfigManager:
    """配置管理器 - 用于保存和加载摄像头配置"""

    def __init__(self, config_file='camera_config.json'):
        self.config_file = config_file
        self.default_config = {
            "camera_type": "ivcam",  # "ivcam" 或 "ip"
            "ip_address": "http://192.168.1.100:8080/video"
        }

    def load_config(self):
        """加载配置文件"""
        if os.path.exists(self.config_file):
            try:
                with open(self.config_file, 'r', encoding='utf-8') as f:
                    config = json.load(f)
                    # 确保包含所有必需的字段
                    for key in self.default_config:
                        if key not in config:
                            config[key] = self.default_config[key]
                    return config
            except Exception as e:
                print(f"加载配置文件失败: {e}，使用默认配置")
                return self.default_config.copy()
        return self.default_config.copy()

    def save_config(self, config):
        """保存配置文件"""
        try:
            with open(self.config_file, 'w', encoding='utf-8') as f:
                json.dump(config, f, indent=4, ensure_ascii=False)
            return True
        except Exception as e:
            print(f"保存配置文件失败: {e}")
            return False


class VideoProcessor:
    def __init__(self, motor_controller, model_path='runs/detect/train3/weights/best.pt',
                 camera_type='ivcam', ip_address=None, gui_callback=None,
                 send_stop_on_connect=False):
        self.motor_controller = motor_controller
        self.throughput_detector = ThroughputDetector(model_path)
        self.paused = True  # 初始状态为暂停
        self.threshold = 50  # 吞吐阈值
        self.waiting_for_start = True  # 等待初始启动
        self.cap = None
        self.frame_count = 0
        self.camera_type = camera_type  # 'ivcam' 或 'ip'
        self.ip_address = ip_address
        self.gui_callback = gui_callback  # GUI回调函数
        self.running = False
        self.processing_thread = None
        self.video_file_path = None  # 视频文件路径
        self.processing_video_file = False  # 是否正在处理视频文件
        self.report_generated = False  # 报告是否已生成（避免重复生成）
        self.send_stop_on_connect = send_stop_on_connect  # 是否在连接时发送一次 stop（默认关闭）

    def init_camera(self):
        """初始化摄像头（支持IVCAM和IP摄像头）"""
        if self.camera_type == 'ip':
            return self._init_ip_camera()
        else:
            return self._init_ivcam_camera()

    def _init_ivcam_camera(self):
        """初始化iVCam摄像头"""
        print("正在尝试初始化iVCam摄像头...")

        # 尝试不同的索引（0, 1, 2, ...）
        self.cap = cv2.VideoCapture(0)  # 通常是0或1
        if not self.cap.isOpened():
            self.cap = cv2.VideoCapture(1)  # 如果0失败，尝试1

        if not self.cap.isOpened():
            print("无法打开摄像头，请检查iVCam是否已连接")
            if self.gui_callback:
                self.gui_callback('error', '无法打开iVCam摄像头，请检查是否已连接')
                return False

        # 设置摄像头分辨率
        self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
        self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
        self.cap.set(cv2.CAP_PROP_FPS, 30)

        print("iVCam摄像头初始化成功")
        return True

    def _init_ip_camera(self):
        """初始化IP摄像头"""
        if not self.ip_address:
            print("IP摄像头地址未设置")
            if self.gui_callback:
                self.gui_callback('error', 'IP摄像头地址未设置')
            return False

        print(f"正在尝试连接IP摄像头: {self.ip_address}")

        # 尝试不同的IP摄像头URL格式
        urls_to_try = [
            self.ip_address,
            f"{self.ip_address}/video",
            f"{self.ip_address}/stream",
            f"{self.ip_address}/mjpeg",
        ]

        for url in urls_to_try:
            self.cap = cv2.VideoCapture(url)
            if self.cap.isOpened():
                # 尝试读取一帧来验证连接
                ret, _ = self.cap.read()
                if ret:
                    print(f"IP摄像头连接成功: {url}")
                    return True
                else:
                    self.cap.release()

        print(f"无法连接到IP摄像头: {self.ip_address}")
        if self.gui_callback:
            self.gui_callback('error', f'无法连接到IP摄像头: {self.ip_address}')
        return False

    def set_camera_config(self, camera_type, ip_address=None):
        """设置摄像头配置"""
        if self.cap is not None:
            self.cap.release()
            self.cap = None

        self.camera_type = camera_type
        self.ip_address = ip_address

    def start_processing(self):
        """开始处理视频流（在单独线程中运行）"""
        if self.running:
            return

        if not self.init_camera():
            return False

        self.running = True
        self.report_generated = False
        self.processing_thread = threading.Thread(target=self._process_loop, daemon=True)
        self.processing_thread.start()
        return True

    def stop_processing(self):
        """停止处理视频流"""
        self.running = False
        if self.processing_thread:
            self.processing_thread.join(timeout=2.0)
        if self.cap:
            self.cap.release()
            self.cap = None

    def _process_loop(self):
        """视频处理循环（在线程中运行）"""
        ret, first_frame = self.cap.read()
        if not ret:
            print("无法从摄像头读取帧")
            if self.gui_callback:
                self.gui_callback('error', '无法从摄像头读取帧')
            return

        height, width = first_frame.shape[:2]

        # 初始状态：视频暂停；是否发送一次 stop 由配置决定
        if self.send_stop_on_connect:
            self.motor_controller.send_command("stop")
        self.paused = True
        self.waiting_for_start = True
        self.throughput_detector.reset()

        print("系统就绪，等待启动...")

        while self.running:
            # 正常处理帧
            if not self.paused and not self.waiting_for_start:
                ret, frame = self.cap.read()
                if not ret:
                    print("无法从摄像头获取帧")
                    if self.gui_callback:
                        self.gui_callback('error', '无法从摄像头获取帧')
                    break

                self.frame_count += 1

                # 在本帧开始时初始化吞吐计数，确保后续可安全使用
                throughput_count = self.throughput_detector.current_throughput

                # 检测中心点
                center_point, confidence = self.throughput_detector.detect_center_point(frame)

                brightness = 0
                if center_point:
                    # 获取中心区域亮度
                    brightness = self.throughput_detector.get_center_region_brightness(frame, center_point)
                    self.throughput_detector.brightness_values.append(brightness)
                    self.throughput_detector.frame_numbers.append(self.frame_count)

                    # 实时更新吞吐数量
                    throughput_count = self.throughput_detector.update_throughput_count(
                        self.frame_count, brightness)
                else:
                    # 未检测到中心点则沿用初始化的吞吐计数
                    pass

                # 先绘制并发送当前帧，确保左上角数据是最新值
                display_frame = self.throughput_detector.draw_detection_info(
                    frame, center_point, brightness, throughput_count)

                # 添加状态信息（自适应字体大小 - 视频文件模式下放大显示）
                frame_height, frame_width = display_frame.shape[:2]
                # 以更友好的下限放大显示，避免小分辨率视频文字过小
                base_font_scale = max(1.0, min(3.0, frame_width / 960.0))
                font_thickness = max(2, int(3 * base_font_scale))

                status = "RUNNING" if not self.paused else "PAUSED"
                status_y = max(200, int(210 * base_font_scale))
                put_text_multilang_bgr(
                    display_frame,
                    f"状态: {'运行中' if status == 'RUNNING' else '已暂停'}",
                    (max(10, int(20 * base_font_scale)), status_y),
                    base_font_scale * 0.9,
                    (255, 0, 0),
                    font_thickness
                )

                threshold_y = max(240, int(250 * base_font_scale))
                put_text_multilang_bgr(
                    display_frame,
                    f"阈值: {self.threshold}",
                    (max(10, int(20 * base_font_scale)), threshold_y),
                    base_font_scale * 0.9,
                    (0, 0, 255),
                    font_thickness
                )

                # 通过回调更新GUI显示（带最新吞吐数）
                if self.gui_callback:
                    self.gui_callback('frame', display_frame)

                # 再检查是否达到阈值（此时已显示最新计数）
                if throughput_count >= self.threshold:
                    # 1) 提示并暂停
                    self.paused = True
                    # 2) 发送 MQTT stop
                    self.motor_controller.send_command("stop")
                    print(f"达到吞吐阈值 {self.threshold}，已暂停并发送 stop")
                    if self.gui_callback:
                        self.gui_callback('threshold_reached', throughput_count)
                    # 3) 关闭摄像头并结束处理线程，使得下次可重新连接
                    self.running = False
                    if self.cap:
                        try:
                            self.cap.release()
                        except Exception:
                            pass
                        self.cap = None
                    if self.gui_callback:
                        self.gui_callback('camera_closed', None)
                    break

            time.sleep(0.033)  # 约30fps

    def start_detection(self):
        """启动检测"""
        if self.waiting_for_start:
            # 初始启动
            self.motor_controller.send_command("start")
            self.paused = False
            self.waiting_for_start = False
            self.throughput_detector.reset()
            print("开始检测，电机启动")
            return True
        elif self.paused:
            # 暂停后恢复
            self.throughput_detector.reset()
            self.motor_controller.send_command("start")
            self.paused = False
            print("恢复检测，电机启动")
            return True
        return False

    def pause_detection(self):
        """暂停/恢复检测"""
        if not self.paused:
            # 暂停
            if not self.processing_video_file:
                self.motor_controller.send_command("stop")
            self.paused = True
            print("检测已暂停" + ("，电机停止" if not self.processing_video_file else ""))
            return True
        else:
            # 恢复
            if not self.processing_video_file:
                self.motor_controller.send_command("start")
            self.paused = False
            print("检测已恢复" + ("，电机启动" if not self.processing_video_file else ""))
            return True

    def reset_detection(self):
        """重置检测"""
        self.throughput_detector.reset()
        self.frame_count = 0
        print("检测已重置")

    def start_video_file_processing(self, video_path):
        """开始处理视频文件（在单独线程中运行）"""
        if self.running:
            return False

        if not os.path.exists(video_path):
            if self.gui_callback:
                self.gui_callback('error', f'视频文件不存在: {video_path}')
            return False

        self.video_file_path = video_path
        self.processing_video_file = True
        self.running = True
        self.report_generated = False
        self.processing_thread = threading.Thread(target=self._process_video_file_loop, daemon=True)
        self.processing_thread.start()
        return True

    def _process_video_file_loop(self):
        """视频文件处理循环（在线程中运行）"""
        cap = cv2.VideoCapture(self.video_file_path)
        if not cap.isOpened():
            print(f"无法打开视频文件: {self.video_file_path}")
            if self.gui_callback:
                self.gui_callback('error', f'无法打开视频文件: {self.video_file_path}')
            self.running = False
            self.processing_video_file = False
            return

        # 获取视频属性
        total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
        fps = cap.get(cv2.CAP_PROP_FPS)
        if fps <= 0:
            fps = 30  # 默认30fps

        # 重置状态
        self.frame_count = 0
        self.paused = False
        self.waiting_for_start = False
        self.throughput_detector.reset()

        # 计算中线（初始值）
        midline = 0.5  # 初始中线值

        print(f"开始处理视频文件: {self.video_file_path}")
        print(f"总帧数: {total_frames}, FPS: {fps}")

        # 计算帧间隔时间（秒）
        frame_interval = 1.0 / fps if fps > 0 else 0.033

        try:
            while self.running:
                ret, frame = cap.read()
                if not ret:
                    # 视频播放完毕
                    print("视频文件处理完成")
                    if self.gui_callback:
                        self.gui_callback('video_complete', self.throughput_detector.current_throughput)
                    break

                self.frame_count += 1

                # 检测中心点
                center_point, confidence = self.throughput_detector.detect_center_point(frame)

                brightness = 0
                if center_point:
                    # 获取中心区域亮度
                    brightness = self.throughput_detector.get_center_region_brightness(frame, center_point)
                    self.throughput_detector.brightness_values.append(brightness)
                    self.throughput_detector.frame_numbers.append(self.frame_count)

                    # 更新中线值
                    if len(self.throughput_detector.brightness_values) > 10:  # 至少有10个样本后再计算中线
                        midline = np.mean(self.throughput_detector.brightness_values)
                        self.throughput_detector.midline = midline

                    # 实时更新吞吐数量
                    throughput_count = self.throughput_detector.update_throughput_count(
                        self.frame_count, brightness)
                else:
                    # 如果没有检测到中心点，使用上一次的吞吐计数
                    throughput_count = self.throughput_detector.current_throughput

                # 显示检测结果
                display_frame = self.throughput_detector.draw_detection_info(
                    frame, center_point, brightness, throughput_count)

                # 添加状态信息（自适应字体大小）
                frame_height, frame_width = display_frame.shape[:2]
                base_font_scale = frame_width / 1280.0
                base_font_scale = max(0.5, min(2.0, base_font_scale))
                font_thickness = max(1, int(2 * base_font_scale))

                # 显示视频文件信息
                status = "PLAYING" if not self.paused else "PAUSED"
                status_y = max(200, int(210 * base_font_scale))
                put_text_multilang_bgr(
                    display_frame,
                    f"状态: {'播放中' if status == 'PLAYING' else '已暂停'}",
                    (max(10, int(20 * base_font_scale)), status_y),
                    base_font_scale,
                    (255, 0, 0),
                    font_thickness
                )

                # 显示进度信息
                progress = (self.frame_count / total_frames * 100) if total_frames > 0 else 0
                progress_y = max(240, int(250 * base_font_scale))
                put_text_multilang_bgr(
                    display_frame,
                    f"Progress: {progress:.1f}% ({self.frame_count}/{total_frames})",
                    (max(10, int(20 * base_font_scale)), progress_y),
                    base_font_scale,
                    (0, 255, 255),
                    font_thickness
                )

                threshold_y = max(280, int(290 * base_font_scale))
                put_text_multilang_bgr(
                    display_frame,
                    f"阈值: {self.threshold}",
                    (max(10, int(20 * base_font_scale)), threshold_y),
                    base_font_scale,
                    (0, 0, 255),
                    font_thickness
                )

                # 通过回调更新GUI显示
                if self.gui_callback:
                    self.gui_callback('frame', display_frame)

                # 如果暂停，等待
                while self.paused and self.running:
                    time.sleep(0.1)

                # 控制播放速度
                time.sleep(frame_interval)

        finally:
            cap.release()
            self.running = False
            self.processing_video_file = False
            print("视频文件处理线程结束")

    def generate_report(self):
        """生成检测报告（保存图表PNG与报告TXT），返回吞吐数量"""
        if len(self.throughput_detector.brightness_values) <= 0:
            return 0

        # 计算最终交点
        final_throughput_count, crossings = self.throughput_detector.detect_midline_crossings()

        # 如果已经生成过报告，则直接返回结果，避免重复保存文件
        if self.report_generated:
            return final_throughput_count

        # 标题/命名
        if self.processing_video_file and self.video_file_path:
            base_name = os.path.splitext(os.path.basename(self.video_file_path))[0]
        else:
            base_name = time.strftime('camera_%Y%m%d_%H%M%S')

        # 保存图表
        plot_path, _, midline_val = self.throughput_detector.plot_analysis(base_name)

        # 保存报告
        os.makedirs('throughput_reports', exist_ok=True)
        report_path = f"throughput_reports/{base_name}_report.txt"
        with open(report_path, 'w', encoding='utf-8') as f:
            f.write("中线交点法吞吐检测报告\n")
            f.write("=" * 50 + "\n")
            f.write(f"名称: {base_name}\n")
            f.write(f"检测时间: {time.strftime('%Y-%m-%d %H:%M:%S')}\n")
            f.write(f"总帧数: {self.frame_count}\n")
            f.write(f"有效亮度帧数: {len(self.throughput_detector.brightness_values)}\n")
            f.write(f"中线值: {midline_val:.3f}\n")
            f.write(f"实时吞吐数量: {self.throughput_detector.current_throughput}\n")
            f.write(f"最终吞吐数量: {final_throughput_count}\n")
            f.write(f"图表: {plot_path if plot_path else '无'}\n")

            f.write("\n有效中线交点（成对）:\n")
            f.write("-" * 40 + "\n")
            for i in range(0, len(crossings), 2):
                if i + 1 < len(crossings):
                    idx1 = crossings[i]
                    idx2 = crossings[i + 1]
                    frame_num1 = self.throughput_detector.frame_numbers[idx1] if idx1 < len(
                        self.throughput_detector.frame_numbers) else idx1
                    frame_num2 = self.throughput_detector.frame_numbers[idx2] if idx2 < len(
                        self.throughput_detector.frame_numbers) else idx2
                    brightness_val1 = self.throughput_detector.brightness_values[idx1] if idx1 < len(
                        self.throughput_detector.brightness_values) else 0
                    brightness_val2 = self.throughput_detector.brightness_values[idx2] if idx2 < len(
                        self.throughput_detector.brightness_values) else 0
                    f.write(f"第{i // 2 + 1:03d}对交点:\n")
                    f.write(f"  交点1 - 帧号: {frame_num1:06d}, 亮度: {brightness_val1:.3f}\n")
                    f.write(f"  交点2 - 帧号: {frame_num2:06d}, 亮度: {brightness_val2:.3f}\n")
                    f.write(f"  交点间距: {abs(frame_num2 - frame_num1)} 帧\n\n")

        print(f"图表已保存: {plot_path}")
        print(f"报告已保存: {report_path}")
        self.report_generated = True
        return final_throughput_count


class GUI:
    """GUI界面主类"""

    def __init__(self, motor_controller, model_path='runs/detect/train3/weights/best.pt'):
        self.motor_controller = motor_controller
        self.model_path = model_path
        self.config_manager = ConfigManager()
        self.video_processor = None

        # 加载配置
        self.config = self.config_manager.load_config()

        # 创建主窗口
        self.root = tk.Tk()
        self.root.title("迈克尔逊干涉检测系统")
        self.root.geometry("1400x800")

        # 设置默认字体（支持中文）
        try:
            # Windows系统常用中文字体
            default_font = ('Microsoft YaHei', 10)
            self.root.option_add('*Font', default_font)
        except:
            pass

        # 视频显示区域
        self.video_label = None
        self.current_frame = None
        self.display_size = None  # 缓存显示区域大小

        # 控制变量
        self.camera_type_var = tk.StringVar(value=self.config.get('camera_type', 'ivcam'))
        self.ip_address_var = tk.StringVar(value=self.config.get('ip_address', ''))
        self.selected_video_path = None  # 选中的视频文件路径

        # 创建界面
        self.create_widgets()

        # 绑定窗口大小变化事件
        self.root.bind('<Configure>', self.on_window_resize)

        # 创建视频处理器（但不立即连接摄像头）
        self.setup_video_processor(connect=False)

        # 启动视频更新循环
        self.update_video_display()

    def create_widgets(self):
        """创建GUI组件"""
        # 主容器
        main_container = tk.Frame(self.root)
        main_container.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

        # 右侧：控制面板（先打包，确保固定宽度）
        control_frame = tk.Frame(main_container, width=350)
        control_frame.pack(side=tk.RIGHT, fill=tk.BOTH, padx=(5, 0))
        control_frame.pack_propagate(False)

        # 创建滚动条和Canvas（使内容宽度自适应Canvas，避免右侧留白）
        canvas = tk.Canvas(control_frame, highlightthickness=0, borderwidth=0)
        scrollbar = ttk.Scrollbar(control_frame, orient="vertical", command=canvas.yview)
        scrollable_frame = tk.Frame(canvas, borderwidth=0)

        scrollable_frame.bind(
            "<Configure>",
            lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
        )

        window_id = canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
        canvas.configure(yscrollcommand=scrollbar.set)

        # 让内部frame宽度跟随canvas宽度变化，消除右侧空白
        def _on_canvas_configure(event):
            try:
                canvas.itemconfig(window_id, width=event.width)
            except Exception:
                pass

        canvas.bind('<Configure>', _on_canvas_configure)

        # 标题
        title_label = tk.Label(scrollable_frame, text="控制面板", font=('Arial', 16, 'bold'))
        title_label.pack(pady=10)

        # 分隔线
        separator = ttk.Separator(scrollable_frame, orient='horizontal')
        separator.pack(fill=tk.X, pady=5)

        # 阈值滑动条（放在最上面）
        threshold_frame = tk.LabelFrame(scrollable_frame, text="阈值控制", padx=10, pady=10)
        threshold_frame.pack(fill=tk.X, padx=10, pady=5)

        self.threshold_var = tk.IntVar(value=50)
        self.threshold_scale = tk.Scale(
            threshold_frame,
            from_=1,
            to=50,
            orient=tk.HORIZONTAL,
            showvalue=True,
            length=260,
            variable=self.threshold_var,
            command=self.on_threshold_changed
        )
        self.threshold_scale.pack(fill=tk.X)

        # 鼠标滚轮支持（Windows和Linux）
        def _on_mousewheel(event):
            # 检查鼠标是否在控制面板区域内
            try:
                x, y = self.root.winfo_pointerx(), self.root.winfo_pointery()
                widget_x = self.root.winfo_rootx() + control_frame.winfo_x()
                widget_y = self.root.winfo_rooty() + control_frame.winfo_y()
                widget_width = control_frame.winfo_width()
                widget_height = control_frame.winfo_height()

                if (widget_x <= x <= widget_x + widget_width and
                        widget_y <= y <= widget_y + widget_height):
                    if hasattr(event, 'delta'):
                        # Windows系统
                        canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
                    elif event.num == 4:
                        # Linux系统向上
                        canvas.yview_scroll(-1, "units")
                    elif event.num == 5:
                        # Linux系统向下
                        canvas.yview_scroll(1, "units")
            except:
                pass

        # Windows系统
        self.root.bind_all("<MouseWheel>", _on_mousewheel)
        # Linux系统
        self.root.bind_all("<Button-4>", _on_mousewheel)
        self.root.bind_all("<Button-5>", _on_mousewheel)

        # 打包Canvas和滚动条
        canvas.pack(side="left", fill="both", expand=True)
        scrollbar.pack(side="right", fill="y")

        # 将所有控件放在scrollable_frame中，而不是control_frame
        # 更新所有后续的control_frame引用为scrollable_frame

        # 摄像头类型选择
        camera_frame = tk.LabelFrame(scrollable_frame, text="摄像头类型", padx=10, pady=10)
        camera_frame.pack(fill=tk.X, padx=10, pady=5)

        ivcam_btn = tk.Radiobutton(camera_frame, text="IVCAM摄像头",
                                   variable=self.camera_type_var, value='ivcam',
                                   command=self.on_camera_type_changed)
        ivcam_btn.pack(anchor=tk.W, pady=2)

        ip_btn = tk.Radiobutton(camera_frame, text="IP摄像头",
                                variable=self.camera_type_var, value='ip',
                                command=self.on_camera_type_changed)
        ip_btn.pack(anchor=tk.W, pady=2)

        # IP地址输入框
        ip_frame = tk.LabelFrame(scrollable_frame, text="IP摄像头配置", padx=10, pady=10)
        ip_frame.pack(fill=tk.X, padx=10, pady=5)

        ip_label = tk.Label(ip_frame, text="IP地址:")
        ip_label.pack(anchor=tk.W, pady=2)

        self.ip_entry = tk.Entry(ip_frame, textvariable=self.ip_address_var, width=30)
        self.ip_entry.pack(fill=tk.X, pady=2)

        save_config_btn = tk.Button(ip_frame, text="保存配置", command=self.save_config)
        save_config_btn.pack(pady=5)

        # 连接摄像头按钮
        connect_frame = tk.LabelFrame(scrollable_frame, text="摄像头连接", padx=10, pady=10)
        connect_frame.pack(fill=tk.X, padx=10, pady=5)

        self.connect_btn = tk.Button(connect_frame, text="连接摄像头",
                                     command=self.connect_camera, bg='#9C27B0', fg='white',
                                     font=('Arial', 11), height=2)
        self.connect_btn.pack(fill=tk.X, pady=2)

        self.disconnect_btn = tk.Button(connect_frame, text="断开摄像头",
                                        command=self.disconnect_camera, bg='#607D8B', fg='white',
                                        font=('Arial', 11), height=2, state=tk.DISABLED)
        self.disconnect_btn.pack(fill=tk.X, pady=2)

        # 更新IP输入框状态
        self.update_ip_entry_state()

        # 控制按钮
        control_btn_frame = tk.LabelFrame(scrollable_frame, text="控制", padx=10, pady=10)
        control_btn_frame.pack(fill=tk.X, padx=10, pady=5)

        self.start_btn = tk.Button(control_btn_frame, text="启动检测",
                                   command=self.start_detection, bg='#4CAF50', fg='white',
                                   font=('Arial', 12), height=2, state=tk.DISABLED)
        self.start_btn.pack(fill=tk.X, pady=5)

        self.pause_btn = tk.Button(control_btn_frame, text="暂停检测",
                                   command=self.pause_detection, bg='#FF9800', fg='white',
                                   font=('Arial', 12), height=2, state=tk.DISABLED)
        self.pause_btn.pack(fill=tk.X, pady=5)

        self.reset_btn = tk.Button(control_btn_frame, text="重置检测",
                                   command=self.reset_detection, bg='#2196F3', fg='white',
                                   font=('Arial', 12), height=2, state=tk.DISABLED)
        self.reset_btn.pack(fill=tk.X, pady=5)

        self.stop_btn = tk.Button(control_btn_frame, text="停止程序",
                                  command=self.stop_program, bg='#F44336', fg='white',
                                  font=('Arial', 12), height=2)
        self.stop_btn.pack(fill=tk.X, pady=5)

        # 按需求移除状态信息区域

        # 视频文件选择区域
        video_file_frame = tk.LabelFrame(scrollable_frame, text="视频文件检测", padx=10, pady=10)
        video_file_frame.pack(fill=tk.X, padx=10, pady=5)

        self.video_file_label = tk.Label(video_file_frame, text="未选择文件",
                                         font=('Arial', 9), anchor=tk.W,
                                         fg='gray', wraplength=300)
        self.video_file_label.pack(fill=tk.X, pady=2)

        self.select_video_btn = tk.Button(video_file_frame, text="选择视频文件",
                                          command=self.select_video_file,
                                          bg='#9E9E9E', fg='white',
                                          font=('Arial', 11), height=2)
        self.select_video_btn.pack(fill=tk.X, pady=2)

        self.start_video_btn = tk.Button(video_file_frame, text="开始检测视频",
                                         command=self.start_video_detection,
                                         bg='#4CAF50', fg='white',
                                         font=('Arial', 11), height=2, state=tk.DISABLED)
        self.start_video_btn.pack(fill=tk.X, pady=2)

        self.stop_video_btn = tk.Button(video_file_frame, text="停止视频检测",
                                        command=self.stop_video_detection,
                                        bg='#F44336', fg='white',
                                        font=('Arial', 11), height=2, state=tk.DISABLED)
        self.stop_video_btn.pack(fill=tk.X, pady=2)

        # 左侧：视频显示区域（后打包，占用剩余空间）
        video_frame = tk.Frame(main_container, bg='black')
        video_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0, 5))

        # 尝试使用支持中文的字体
        try:
            # Windows系统常用中文字体
            chinese_font = ('Microsoft YaHei', 'SimHei', 'SimSun', 'Arial')
        except:
            chinese_font = ('Arial',)

        if not HAS_PIL:
            # 如果没有PIL，显示提示信息
            video_label = tk.Label(video_frame,
                                   text="PIL/Pillow未安装\n\n请运行: pip install Pillow\n\n安装后重启程序",
                                   bg='black', fg='yellow',
                                   font=('Arial', 14), justify=tk.CENTER)
            video_label.pack(fill=tk.BOTH, expand=True)
            self.video_label = video_label
        else:
            video_label = tk.Label(video_frame, text="等待摄像头画面...", bg='black', fg='white',
                                   font=('Arial', 14))
            video_label.pack(fill=tk.BOTH, expand=True)
            self.video_label = video_label

    def on_window_resize(self, event):
        """窗口大小变化时的回调"""
        # 重置显示大小缓存，强制重新计算
        if event.widget == self.root:
            self.display_size = None

    def on_threshold_changed(self, value):
        """阈值滑动条变化回调"""
        try:
            v = int(float(value))
        except Exception:
            v = 50
        if self.video_processor:
            self.video_processor.threshold = v

    def update_ip_entry_state(self):
        """更新IP地址输入框的状态"""
        if hasattr(self, 'ip_entry'):
            state = 'normal' if self.camera_type_var.get() == 'ip' else 'disabled'
            self.ip_entry.config(state=state)

    def on_camera_type_changed(self):
        """摄像头类型改变时的回调"""
        self.update_ip_entry_state()
        # 如果已经连接了摄像头，需要先断开再重新连接
        if self.video_processor and self.video_processor.running:
            self.disconnect_camera()
        # 更新配置但不立即连接
        if self.video_processor:
            camera_type = self.camera_type_var.get()
            ip_address = self.ip_address_var.get() if camera_type == 'ip' else None
            self.video_processor.set_camera_config(camera_type, ip_address)

    def save_config(self):
        """保存配置"""
        config = {
            'camera_type': self.camera_type_var.get(),
            'ip_address': self.ip_address_var.get()
        }
        if self.config_manager.save_config(config):
            self.config = config
            messagebox.showinfo("成功", "配置已保存")
            # 更新视频处理器配置（但不立即连接）
            if self.video_processor:
                camera_type = self.camera_type_var.get()
                ip_address = self.ip_address_var.get() if camera_type == 'ip' else None
                self.video_processor.set_camera_config(camera_type, ip_address)
        else:
            messagebox.showerror("错误", "配置保存失败")

    def setup_video_processor(self, connect=False):
        """设置视频处理器（不立即连接摄像头）"""
        camera_type = self.config.get('camera_type', 'ivcam')
        ip_address = self.config.get('ip_address') if camera_type == 'ip' else None

        self.video_processor = VideoProcessor(
            self.motor_controller,
            self.model_path,
            camera_type=camera_type,
            ip_address=ip_address,
            gui_callback=self.on_video_update
        )

        # 如果connect=True，则连接摄像头
        if connect:
            self.connect_camera()

    def connect_camera(self):
        """连接摄像头"""
        if not self.video_processor:
            return

        # 更新摄像头配置
        camera_type = self.camera_type_var.get()
        ip_address = self.ip_address_var.get() if camera_type == 'ip' else None
        self.video_processor.set_camera_config(camera_type, ip_address)

        # 如果IP摄像头但地址为空，提示用户
        if camera_type == 'ip' and not ip_address:
            messagebox.showwarning("警告", "请先输入IP摄像头地址")
            return

        # 尝试连接摄像头
        self.connect_btn.config(state=tk.DISABLED)
        self.root.update()

        if self.video_processor.start_processing():
            self.connect_btn.config(state=tk.DISABLED)
            self.disconnect_btn.config(state=tk.NORMAL)
            # 启用检测控制按钮
            self.start_btn.config(state=tk.NORMAL)
            self.pause_btn.config(state=tk.NORMAL)
            self.reset_btn.config(state=tk.NORMAL)
        else:
            self.connect_btn.config(state=tk.NORMAL)
            self.disconnect_btn.config(state=tk.DISABLED)
            # 禁用检测控制按钮
            self.start_btn.config(state=tk.DISABLED)
            self.pause_btn.config(state=tk.DISABLED)
            self.reset_btn.config(state=tk.DISABLED)
            messagebox.showerror("连接失败", f"无法连接到{'IP摄像头' if camera_type == 'ip' else 'IVCAM摄像头'}")

    def disconnect_camera(self):
        """断开摄像头"""
        if self.video_processor:
            self.video_processor.stop_processing()
            self.connect_btn.config(state=tk.NORMAL)
            self.disconnect_btn.config(state=tk.DISABLED)
            self.current_frame = None  # 清空当前帧
            # 禁用检测控制按钮
            self.start_btn.config(state=tk.DISABLED)
            self.pause_btn.config(state=tk.DISABLED)
            self.reset_btn.config(state=tk.DISABLED)

    def select_video_file(self):
        """选择视频文件"""
        # 打开文件选择对话框
        video_file = filedialog.askopenfilename(
            title="选择视频文件",
            filetypes=[
                ("视频文件", "*.mp4 *.avi *.mov *.mkv *.flv *.wmv"),
                ("所有文件", "*.*")
            ]
        )

        if video_file:
            self.selected_video_path = video_file
            # 显示文件名（如果太长则截断）
            filename = os.path.basename(video_file)
            if len(filename) > 40:
                filename = filename[:37] + "..."
            self.video_file_label.config(text=f"已选择: {filename}", fg='green')
            self.start_video_btn.config(state=tk.NORMAL)
            print(f"已选择视频文件: {video_file}")

    def start_video_detection(self):
        """开始视频文件检测"""
        if not hasattr(self, 'selected_video_path') or not self.selected_video_path:
            messagebox.showwarning("警告", "请先选择视频文件")
            return

        # 如果正在处理摄像头，先停止
        if self.video_processor and self.video_processor.running:
            if not self.video_processor.processing_video_file:
                self.disconnect_camera()

        # 开始处理视频文件
        if self.video_processor.start_video_file_processing(self.selected_video_path):
            self.start_video_btn.config(state=tk.DISABLED)
            self.stop_video_btn.config(state=tk.NORMAL)
            self.select_video_btn.config(state=tk.DISABLED)
            # 禁用摄像头相关按钮
            self.connect_btn.config(state=tk.DISABLED)
            self.disconnect_btn.config(state=tk.DISABLED)
            self.start_btn.config(state=tk.DISABLED)
            self.pause_btn.config(state=tk.NORMAL)  # 视频文件可以暂停
            self.reset_btn.config(state=tk.DISABLED)
            print(f"开始处理视频文件: {self.selected_video_path}")
        else:
            messagebox.showerror("错误", "无法开始处理视频文件")

    def stop_video_detection(self):
        """停止视频文件检测"""
        if self.video_processor:
            self.video_processor.stop_processing()
            self.start_video_btn.config(state=tk.NORMAL)
            self.stop_video_btn.config(state=tk.DISABLED)
            self.select_video_btn.config(state=tk.NORMAL)
            # 恢复摄像头相关按钮
            self.connect_btn.config(state=tk.NORMAL)
            self.disconnect_btn.config(state=tk.DISABLED)
            self.start_btn.config(state=tk.DISABLED)
            self.pause_btn.config(state=tk.DISABLED)
            self.reset_btn.config(state=tk.DISABLED)
            self.current_frame = None  # 清空当前帧

            # 生成报告（在 video_complete 事件里也会触发，这里作为兜底，避免重复生成由内部flag控制）
            if self.video_processor:
                final_count = self.video_processor.generate_report()
                if self.video_processor.report_generated:
                    messagebox.showinfo("完成", f"视频文件处理完成\n最终吞吐数量: {final_count}")

    def on_video_update(self, event_type, data):
        """视频更新回调"""
        if event_type == 'frame':
            self.current_frame = data
        elif event_type == 'error':
            messagebox.showerror("错误", data)
        elif event_type == 'threshold_reached':
            messagebox.showinfo("提示", f"已达到吞吐阈值 {data}")
        elif event_type == 'video_complete':
            # 视频文件处理完成
            self.start_video_btn.config(state=tk.NORMAL)
            self.stop_video_btn.config(state=tk.DISABLED)
            self.select_video_btn.config(state=tk.NORMAL)
            # 恢复摄像头相关按钮
            self.connect_btn.config(state=tk.NORMAL)
            self.disconnect_btn.config(state=tk.DISABLED)
            self.start_btn.config(state=tk.DISABLED)
            self.pause_btn.config(state=tk.DISABLED)
            self.reset_btn.config(state=tk.DISABLED)

            # 生成报告（此处调用，内部有去重标记避免重复生成）
            if self.video_processor:
                final_count = self.video_processor.generate_report()
                if self.video_processor.report_generated:
                    messagebox.showinfo("完成", f"视频文件处理完成\n最终吞吐数量: {final_count}")
        elif event_type == 'camera_closed':
            # 摄像头已在后台线程关闭，这里做 UI 状态复位，允许再次连接
            self.connect_btn.config(state=tk.NORMAL)
            self.disconnect_btn.config(state=tk.DISABLED)
            self.start_btn.config(state=tk.DISABLED)
            self.pause_btn.config(state=tk.DISABLED)
            self.reset_btn.config(state=tk.DISABLED)
            self.current_frame = None

    def update_video_display(self):
        """更新视频显示"""
        if not HAS_PIL:
            # 如果没有PIL，不更新视频显示（保持提示信息）
            pass
        elif self.current_frame is not None:
            try:
                # 获取显示区域大小（使用缓存避免频繁查询）
                try:
                    # 获取video_frame的实际大小（这是实际可用的显示区域）
                    video_frame = self.video_label.master
                    max_height = video_frame.winfo_height()
                    max_width = video_frame.winfo_width()

                    # 如果窗口大小变化，更新缓存
                    if max_height > 1 and max_width > 1:
                        self.display_size = (max_width, max_height)
                    elif self.display_size:
                        max_width, max_height = self.display_size
                    else:
                        # 首次运行时使用默认大小（考虑控制面板宽度350）
                        max_width, max_height = 1000, 600
                        self.display_size = (max_width, max_height)
                except:
                    # 如果无法获取大小，使用缓存或默认值
                    if self.display_size:
                        max_width, max_height = self.display_size
                    else:
                        max_width, max_height = 1000, 600
                        self.display_size = (max_width, max_height)

                # 确保有有效的大小
                if max_width <= 1 or max_height <= 1:
                    max_width, max_height = 1000, 600

                # 获取原始帧大小
                frame_height, frame_width = self.current_frame.shape[:2]

                # 计算缩放比例，使视频适应显示区域（保持宽高比，不裁剪）
                scale_width = max_width / frame_width
                scale_height = max_height / frame_height
                scale = min(scale_width, scale_height)  # 使用较小的缩放比例，确保完全显示

                # 计算新尺寸
                new_width = int(frame_width * scale)
                new_height = int(frame_height * scale)

                # 调整帧大小
                resized_frame = cv2.resize(self.current_frame, (new_width, new_height))

                # 创建黑色背景，将缩放后的画面居中放置
                display_frame = np.zeros((max_height, max_width, 3), dtype=np.uint8)
                start_y = (max_height - new_height) // 2
                start_x = (max_width - new_width) // 2
                display_frame[start_y:start_y + new_height, start_x:start_x + new_width] = resized_frame

                # 转换BGR到RGB
                rgb_frame = cv2.cvtColor(display_frame, cv2.COLOR_BGR2RGB)

                # 转换为PIL Image
                image = Image.fromarray(rgb_frame)
                photo = ImageTk.PhotoImage(image=image)

                # 更新标签
                self.video_label.config(image=photo, text="")
                self.video_label.image = photo  # 保持引用
            except Exception as e:
                print(f"更新视频显示错误: {e}")
                import traceback
                traceback.print_exc()
        else:
            # 如果没有视频帧，显示等待画面
            try:
                # 获取显示区域大小
                try:
                    max_height = max(self.video_label.winfo_height(), 480)
                    max_width = max(self.video_label.winfo_width(), 640)
                except:
                    max_width, max_height = 640, 480

                # 创建一个简单的等待画面，填满显示区域
                wait_frame = np.zeros((max_height, max_width, 3), dtype=np.uint8)
                # 使用英文避免字体问题
                text = "Waiting for camera..."
                font_scale = max_width / 640  # 根据显示区域调整字体大小
                cv2.putText(wait_frame, text,
                            (int(max_width * 0.2), int(max_height * 0.5)),
                            cv2.FONT_HERSHEY_SIMPLEX, font_scale, (255, 255, 255), 2)
                rgb_frame = cv2.cvtColor(wait_frame, cv2.COLOR_BGR2RGB)
                image = Image.fromarray(rgb_frame)
                photo = ImageTk.PhotoImage(image=image)
                self.video_label.config(image=photo, text="")
                self.video_label.image = photo
            except Exception as e:
                print(f"创建等待画面错误: {e}")
                self.video_label.config(image="", text="Waiting for camera...")

        # 移除状态信息更新（按需求不再显示）

        # 每33ms更新一次（约30fps）
        self.root.after(33, self.update_video_display)

    def start_detection(self):
        """启动检测"""
        if self.video_processor:
            if self.video_processor.start_detection():
                pass

    def pause_detection(self):
        """暂停/恢复检测"""
        if self.video_processor:
            if self.video_processor.pause_detection():
                if self.video_processor.processing_video_file:
                    if self.video_processor.paused:
                        self.pause_btn.config(text="恢复检测")
                    else:
                        self.pause_btn.config(text="暂停检测")
                else:
                    if self.video_processor.paused:
                        self.pause_btn.config(text="恢复检测")
                    else:
                        self.pause_btn.config(text="暂停检测")

    def reset_detection(self):
        """重置检测"""
        if self.video_processor:
            self.video_processor.reset_detection()
            pass

    def stop_program(self):
        """停止程序"""
        if self.video_processor:
            self.video_processor.stop_processing()
        self.motor_controller.send_command("stop")
        self.motor_controller.stop()
        self.root.quit()
        self.root.destroy()

    def run(self):
        """运行GUI"""
        self.root.protocol("WM_DELETE_WINDOW", self.stop_program)
        self.root.mainloop()


def main():
    # 初始化电机控制器（MQTTS）
    motor_controller = MqttController()
    motor_controller.connect()

    # 初始化视频处理器和吞吐检测器
    # 请确保修改为正确的模型路径
    model_path = 'runs/detect/train3/weights/best.pt'  # 修改为你的模型路径

    try:
        # 创建并运行GUI
        app = GUI(motor_controller, model_path)
        app.run()

        # 摄像头模式下如需手动生成，可通过GUI触发；不在此处自动生成以避免重复

    except Exception as e:
        print(f"发生错误: {e}")
        import traceback
        traceback.print_exc()
    finally:
        # 确保电机停止并断开 MQTT
        motor_controller.stop()
        print("程序结束")


if __name__ == "__main__":
    main()
